package mm.test;

import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.List;

import javax.servlet.http.HttpServletRequest;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import mm.util.XMLUtils;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;

// FIXME NOTICE: All html string are considered as lower case now!
public class MockResponseImpl extends MockResponseAdapter implements MockResponse {
		
	private static final Log LOG=LogFactory.getLog(MockResponseImpl.class);

	private HttpServletRequest mockRequest;
	private String redirectedTo;
	private StringWriter stringWriter=new StringWriter();
	private PrintWriter printWriter=new PrintWriter(stringWriter);
	private List<String> filterTour;
	private String content;
	private Document document;
	
	private String contentType;

	public String getContent() {
		if(content==null)
			content=stringWriter.getBuffer().toString();
		return content;
	}

	public List<String> getFilterTour() {
		return filterTour;
	}

	public void setFilterTour(List<String> filterTour) {
		this.filterTour = filterTour;
	}

	public MockResponseImpl(HttpServletRequest mockRequest, List<String> filterTour) {
		super();
		this.mockRequest = mockRequest;
		this.filterTour = filterTour;
	}

	public String getRequestServletPath() {
		return mockRequest.getServletPath();
	}

	@Override
	public void sendRedirect(String href) {
		printWriter=new PrintWriter(new StringWriter());
		redirectedTo=findServletPathFromHref(href);
	}

	@Override
	public PrintWriter getWriter() throws IOException {
		return printWriter;
	}

	@Override
	public String getCharacterEncoding() {
		return "UTF-8";
	}

	@Override
	public void setContentType(String contentType) {
		this.contentType=contentType;
	}

	@Override
	public String getContentType() {
		return contentType;
	}

	public String getRedirectedTo() {
		return redirectedTo;
	}

	public MockRequest getRedirectedRequest() {
		MockRequest newMockRequest=((MockSession)mockRequest.getSession()).createRequest();
		newMockRequest.setServletPath(getRedirectedTo());
		return newMockRequest;
	}

	private boolean contains(Element element) {
		NodeList list = getDocument().getElementsByTagName("*");
		for(int i=0;i<list.getLength();i++){
			if(list.item(i)==element){
				return true;
			}
		}
		return false;
	}

	public Element getElement(String id) {
		NodeList list = getDocument().getElementsByTagName("*");
		for(int i=0;i<list.getLength();i++){
			Element element=(Element)list.item(i);
			if(id.equals(element.getAttribute("id"))){
				return element;
			}
		}
		return null;
	}

	public MockResponse click(Element element) {
		if(!contains(element))
			throw new RuntimeException("element not found!"); // FIXME
		return doClick(element);
	}

	public MockResponse click(String id) {
		Element element=getElement(id);
		if(element==null)
			throw new RuntimeException("element '"+id+"' not found!"); // FIXME
		return doClick(element);
	}

	private MockResponse doClick(Element element) {
		if(element.getNodeName().equals("a")){
			String href=element.getAttribute("href");
			if(href!=null&&href.trim().length()>0){
				MockRequest newMockRequest=((MockSession)mockRequest.getSession()).createRequest();
				newMockRequest.setServletPath(findServletPathFromHref(href));
				return newMockRequest.submit();
			}
		}else if(element.getNodeName().equals("input")){
			String type=element.getAttribute("type");
			if("submit".equals(type)){
				NodeList forms = getDocument().getElementsByTagName("form");
				for(int i=0;i<forms.getLength();i++){
					Element form = (Element)forms.item(i);
					NodeList inputs = form.getElementsByTagName("input");
					for(int j=0;j<inputs.getLength();j++){
						if(inputs.item(j)==element){
							return doSubmitForm(form);
						}
					}
				}
			}
		}
		// TODO inputs, javascripts...
		LOG.debug("An unknown click action...nothing to do.");
		return null;
	}
	
	private String findServletPathFromHref(String href){
		// removed for below-Servlet-spec-v2.5 container
//		String contextPath=mockRequest.getSession().getServletContext().getContextPath();
		String contextPath=mockRequest.getContextPath();
		if(href==null||href.length()<1){
			href=contextPath+mockRequest.getServletPath();
			LOG.debug(href);
		}
		// only handle absolute full path now (starting with '/' and preffixed with context path)
		if(href.indexOf(contextPath)!=0){
			throw new RuntimeException("The context can't handle the request '"+href+"'");
		}
		return href.substring(contextPath.length());
	}

	private MockResponse doSubmitForm(Element form) {
		MockRequest newMockRequest=((MockSession)mockRequest.getSession()).createRequest();
		String action=form.getAttribute("action");
		newMockRequest.setServletPath(findServletPathFromHref(action));
		String method=form.getAttribute("method");
		if(method!=null&&method.length()>0){
			newMockRequest.setMethod(MockRequest.METHOD.valueOf(method));
		}
		NodeList inputs = form.getElementsByTagName("input"); // TOOD selects, textareas...
		for(int i=0;i<inputs.getLength();i++){
			Element input = (Element)inputs.item(i);
			String name=input.getAttribute("name");
			String value=input.getAttribute("value");
			if(name!=null&&name.length()>0
					&&value!=null&&value.length()>0){
				newMockRequest.setParameter(name, value);
			}
		}
		return newMockRequest.submit();
	}

	public MockResponse submitForm(Element form) {
		if(!contains(form))
			throw new RuntimeException("element not found!"); // FIXME
		return doSubmitForm(form);
	}

	public Document getDocument() {
		if(document==null)
			document=XMLUtils.buildDocument(getContent());
		return document;
	}

	public void setAttribute(Element element, String name, String value) {
		if(!contains(element))
			throw new RuntimeException("element not found!"); // FIXME
		element.setAttribute(name, value);
	}

	public void setInputValue(Element element, String value) {
		if("input".equals(element.getNodeName())){
			setAttribute(element, "value", value);
		}
		// TODO others. (don't forget to check if the element is contained in document)
	}

	public void mergeFrom(MockResponse newRes) {
		MockResponseImpl newResImpl=(MockResponseImpl)newRes;
		this.content=newResImpl.content;
		this.document=newResImpl.document;
		this.filterTour=newResImpl.filterTour;
		this.mockRequest=newResImpl.mockRequest;
		this.printWriter=newResImpl.printWriter;
		this.redirectedTo=newResImpl.redirectedTo;
		this.stringWriter=newResImpl.stringWriter;
	}

}
